﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TrinityAPI.Base.Models;

namespace TrinityAPI.Base.Tools
{
    public static class FunctionHelper
    {
        /// <summary>
        /// 计算参数结果
        /// </summary>
        /// <param name="parameter">参数实体</param>
        /// <returns>计算结果</returns>
        public static JToken Evaluate(this Parameter parameter)
        {
            if (parameter == null)
                return null;
            return parameter.Functions.Execute(parameter.Value);
        }

        /// <summary>
        /// 执行预设函数
        /// </summary>
        /// <param name="funcs">函数集合</param>
        /// <param name="value">操作数</param>
        /// <returns>执行结果</returns>
        public static JToken Execute(this List<Function> funcs, JToken value = null)
        {
            if (funcs == null || funcs.Count == 0)
                return value;
            JToken result = null;
            for (int i = 0; i < funcs.Count; i++)
            {
                result = funcs[i].Execute(i == 0 ? value : result);
            }
            return result;
        }

        /// <summary>
        /// 执行预设函数
        /// </summary>
        /// <param name="func">函数</param>
        /// <param name="value">操作数</param>
        /// <returns>执行结果</returns>
        public static JToken Execute(this Function func, JToken value = null)
        {
            if (func == null)
                return value;
            JToken result = null;
            string funcName = func.Name?.ToLower();
            switch (funcName)
            {
                case "toint":
                    int defIntValue = 0;
                    if (func.Args.Length > 0 && func.Args[0] is JValue)
                        defIntValue = Decimal.ToInt32(DecimalHelper.ToDecimal(((JValue)func.Args[0]).Value));
                    result = Utils.TryConvertToInt(value, defIntValue);
                    break;
                case "todecimal":
                    decimal defDecimalValue = 0;
                    if (func.Args.Length > 0 && func.Args[0] is JValue)
                        defDecimalValue = DecimalHelper.ToDecimal(((JValue)func.Args[0]).Value);
                    result = Utils.TryConvertToDecimal(value, defDecimalValue);
                    break;
                case "tostring":
                    if (value is JValue)
                        result = func.Args.Length > 0 && !string.IsNullOrWhiteSpace(func.Args[0]?.ToString()) ? ((JValue)value).ToString(func.Args[0].ToString()) : ((JValue)value).ToString();
                    else
                        result = value?.ToString(func.Args.Length > 0 && func.Args[0]?.ToString() == "1" ? Formatting.Indented : Formatting.None);
                    break;
                case "trim":
                    result = value?.Type == JTokenType.String ? value.ToString().Trim() : string.Empty;
                    break;
                case "toupper":
                    result = value?.Type == JTokenType.String ? value.ToString().ToUpper() : string.Empty;
                    break;
                case "tolower":
                    result = value?.Type == JTokenType.String ? value.ToString().ToLower() : string.Empty;
                    break;
                case "tojsonobject":
                case "jsonstringtoobject":
                    result = value?.Type == JTokenType.String ? JSONHelper.JsonToJObject(value.ToString()) : null;
                    break;
                case "simplearraytoobjectarray":
                    result = Utils.SimpleArrayToObjectArray(value, func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String ? func.Args[0].ToString() : null);
                    break;
                case "objectarraytosimplearray":
                    result = Utils.ObjectArrayToSimpleArray(value, func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String ? func.Args[0].ToString() : null);
                    break;
                case "sum":
                    result = func.Args.Length > 0 ? Utils.SumTotal(value, func.Args[0]?.ToString()) : 0;
                    break;
                case "count":
                    result = value?.Type == JTokenType.Array ? value.Count() : 0;
                    break;
                case "select":
                    result = Utils.SelectSomeFields(value, func.Args.Select(i => i?.Type == JTokenType.String ? i.ToString() : null).ToArray());
                    break;
                case "guid":
                    if (func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String)
                        try
                        {
                            result = Guid.NewGuid().ToString(func.Args[0].ToString());
                        }
                        catch
                        {
                            result = Guid.NewGuid().ToString();
                        }
                    else
                        result = Guid.NewGuid().ToString();
                    break;
                case "now":
                    if (func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String)
                        try
                        {
                            result = DateTime.Now.ToString(func.Args[0].ToString());
                        }
                        catch
                        {
                            result = DateTime.Now.ToString();
                        }
                    else
                        result = DateTime.Now.ToString();
                    break;
                case "utcnow":
                    if (func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String)
                        try
                        {
                            result = DateTime.UtcNow.ToString(func.Args[0].ToString());
                        }
                        catch
                        {
                            result = DateTime.UtcNow.ToString();
                        }
                    else
                        result = DateTime.UtcNow.ToString();
                    break;
                case "where":
                case "whereand":
                    result = func.Args.Length > 1 ? Utils.Filter(value, i =>
                    {
                        for (int argIndex = 0; argIndex < func.Args.Length; argIndex += 2)
                        {
                            if (!Utils.FilterComparator(i as JObject, func.Args[argIndex]?.ToString(), argIndex + 1 < func.Args.Length ? func.Args[argIndex + 1] : null))
                                return false;
                        }
                        return true;
                    }) : value;
                    break;
                case "whereor":
                    result = func.Args.Length > 1 ? Utils.Filter(value, i =>
                    {
                        for (int argIndex = 0; argIndex < func.Args.Length; argIndex += 2)
                        {
                            if (Utils.FilterComparator(i as JObject, func.Args[argIndex]?.ToString(), argIndex + 1 < func.Args.Length ? func.Args[argIndex + 1] : null))
                                return true;
                        }
                        return false;
                    }) : value;
                    break;
                case "trygetsort":
                    if (func.Args.Length > 0)
                    {
                        if (func.Args.Length > 2)
                            result = Utils.Filter(value, i =>
                            {
                                return (i as JObject).GetJsonObjectBaseValue("groupid") == null ||
                                    (i as JObject).GetJsonObjectBaseValue("groupid").ToString().Equals(func.Args[2]?.ToString(), StringComparison.OrdinalIgnoreCase);
                            });
                        else
                            result = value;
                        if (result == null || result.Type != JTokenType.Array || !result.HasValues)
                        {
                            var def = new JObject();
                            def.Add("field", func.Args[0]);
                            def.Add("mode", func.Args.Length > 1 && func.Args[1]?.ToString()?.ToLower() == "desc" ? "desc" : "asc");
                            result = new JArray() { def };
                        }
                    }
                    break;
                case "getfilter":
                    if (func.Args.Length > 0)
                    {
                        result = Utils.Filter(value, i =>
                        {
                            return (i as JObject).GetJsonObjectBaseValue("groupid") == null ||
                                (i as JObject).GetJsonObjectBaseValue("groupid").ToString().Equals(func.Args[0]?.ToString(), StringComparison.OrdinalIgnoreCase);
                        });
                    }
                    break;
                case "split":
                    if (value != null && value.Type == JTokenType.String && func.Args.Length > 0 && func.Args[0]?.ToString().Length == 1)
                    {
                        try
                        {
                            result = new JArray(value.ToString().Split(Convert.ToChar(func.Args[0])));
                        }
                        catch
                        {
                        }
                    }
                    break;
                case "md5":
                case "sha1":
                    if (value?.Type == JTokenType.String)
                    {
                        Encoding encoding = Encoding.UTF8;
                        bool isUpper = false;
                        bool isBase64 = false;
                        if (func.Args.Length > 0)
                        {
                            try
                            {
                                encoding = Encoding.GetEncoding(func.Args[0]?.ToString());
                            }
                            catch
                            {
                                encoding = Encoding.UTF8;
                            }
                            isUpper = func.Args.Length > 1 && func.Args[1]?.ToString() == "true";
                            isBase64 = func.Args.Length > 2 && func.Args[2]?.ToString() == "true";
                        }
                        if (funcName == "md5")
                            result = Utils.MD5(value.ToString(), encoding, isUpper, isBase64);
                        else if (funcName == "sha1")
                            result = Utils.GetSha1Hash(value.ToString(), encoding, isUpper, isBase64);
                    }
                    else
                        result = string.Empty;
                    break;
                case "tobase64":
                case "restorefrombase64":
                    if (value?.Type == JTokenType.String)
                    {
                        Encoding encoding = Encoding.UTF8;
                        if (func.Args.Length > 0)
                        {
                            try
                            {
                                encoding = Encoding.GetEncoding(func.Args[0]?.ToString());
                            }
                            catch
                            {
                                encoding = Encoding.UTF8;
                            }
                        }
                        result = Utils.Base64Conversion(value.ToString(), funcName == "tobase64", encoding);
                    }
                    else
                        result = string.Empty;
                    break;
                case "toxml":
                case "parseformxml":
                    result = Utils.XMLConversion(value, funcName == "toxml");
                    break;
                case "first":
                case "last":
                    if (value?.Type == JTokenType.Array && value.Count() > 0)
                    {
                        int index = funcName == "first" ? 0 : value.Count() - 1;
                        result = value.Count() > index ? value[index] : null;
                    }
                    break;
                case "getitem":
                    if (value?.Type == JTokenType.Array && value.Count() > 0 && func.Args.Length > 0 && (func.Args[0]?.Type == JTokenType.Integer || func.Args[0]?.Type == JTokenType.String)) {
                        int index = 0;
                        if (func.Args[0].Type == JTokenType.Integer)
                            index = func.Args[0].Value<int>();
                        else if (!int.TryParse(func.Args[0].ToString(), out index))
                            return null;
                        result = value.Count() > index ? value[index] : null;
                    }
                    break;
                case "getproperty":
                    if (value?.Type == JTokenType.Object && value.Count() > 0 && func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String)
                    {
                        result = (value as JObject).GetJsonObjectValue(func.Args[0].ToString());
                    }
                    break;
                case "concatwith":
                    if ((value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined || value.Type == JTokenType.String) || func.Args.Length > 1 && func.Args[0]?.Type == JTokenType.String)
                    {
                        JToken[] rest = new JToken[func.Args.Length - 1];
                        Array.Copy(func.Args, 1, rest, 0, rest.Length);
                        result = Utils.ConcatItems(func.Args[0].ToString(), value, rest);
                    }
                    else
                        result = value;
                    break;
                case "concat":
                    result = Utils.ConcatItems(null, value, func.Args);
                    break;
                case "add":
                case "subtract":
                case "multiply":
                case "divide":
                    if (value is JValue)
                    {
                        object arg1 = 0;
                        List<object> otherArgs = new List<object>();
                        for (var i = 0; i < func.Args.Length; i++)
                        {
                            object arg = func.Args[i] is JValue ? ((JValue)func.Args[i]).Value : 0;
                            if (i == 0)
                                arg1 = arg;
                            else
                                otherArgs.Add(arg);
                        }
                        if (funcName == "add")
                            result = DecimalHelper.Add(((JValue)value).Value, arg1, otherArgs.ToArray());
                        else if (funcName == "subtract")
                            result = DecimalHelper.Subtract(((JValue)value).Value, arg1, otherArgs.ToArray());
                        else if (funcName == "multiply")
                            result = DecimalHelper.Multiply(((JValue)value).Value, arg1, otherArgs.ToArray());
                        else if (funcName == "divide")
                            result = DecimalHelper.Divide(((JValue)value).Value, arg1, otherArgs.ToArray());
                    }
                    else
                        result = 0;
                    break;
                case "nullif":
                    result = func.Args.Length > 0 && value?.ToString() == func.Args[0]?.ToString() || func.Args.Length == 0 && string.IsNullOrWhiteSpace(value?.ToString()) ? null : value;
                    break;
                case "isnull":
                    result = value == null || value.Type == JTokenType.Null ? func.Args.Length > 0 ? func.Args[0] : string.Empty : value;
                    break;
                case "if":
                    result = func.Args.Length == 3 ? value?.ToString() == func.Args[0]?.ToString() ? func.Args[1] : func.Args[2] : null;
                    break;
                case "equals":
                    result = func.Args.Length == 1 && value?.ToString() == func.Args[0]?.ToString() ? true : false;
                    break;
                case "join":
                    if (value is JArray && value.HasValues)
                    {
                        string separator = func.Args.Length > 0 && func.Args[0]?.Type == JTokenType.String ? func.Args[0].ToString() : string.Empty;
                        List<string> list = new List<string>();
                        foreach (var item in (JArray)value)
                        {
                            if (item is JValue)
                                list.Add(item.ToString());
                        }
                        result = string.Join(separator, list);
                    }
                    else
                        result = string.Empty;
                    break;
                default:
                    if (func.Name != null && APIContext.ScriptEngine != null && APIContext.ScriptEngine.Script != null && !(APIContext.ScriptEngine.Script[func.Name] is Microsoft.ClearScript.Undefined))
                    {
                        JArray args = value?.Type == JTokenType.Undefined ? new JArray(func.Args) : new JArray(value, func.Args);
                        result = Utils.CallJSFunction(func.Name, args.ToArray());
                    }
                    break;
            }
            return result;
        }
    }
}